package org.lzuploader.gui;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.ProgressBar;
import org.eclipse.swt.widgets.Shell;
import org.lzuploader.exception.ThreadAlreadyRunningException;
import org.lzuploader.model.Localization;

/** This class displays a window that cannot be closed, asking the user to wait.
 * 
 * @author Emmanuel Cron
 *
 */
public final class WWait
{
    /** Unique instance of this class. */
    private static WWait instance;
    
    /** Width of the window. */
    private static final int WINDOW_WIDTH = 225;
    /** Height of the window. */
    private static final int WINDOW_HEIGHT = 70;
    
    /** Logger of this class. */
    private static Log log = LogFactory.getLog(WWait.class);
    /** Localization of this class. */
    private static Localization localization = Localization.getInstance();
    
    /** Shell used to display this window. */
    private Shell shell;
    
    /** Shell of the currently waiting window. */
    private Shell windowThatCalled;
    
    /* COMPONENTS */
    
    /** Progress bar used to show that program isn't idling. */ 
    private ProgressBar pBar;
    
    /* END OF COMPONENTS */
    
    /** User should not be able to instantiate this class.
     * You should use getInstance() instead.
     */
    private WWait()
    {
        log.debug("Creating WWait object.");
    }
    
    /** Returns the instance of this class or a new one if none exists.
     * 
     * @return A WWait instance
     */
    public static WWait getInstance()
    {
        if (instance == null)
        {
            instance = new WWait();
        }
        
        return instance;
    }
    
    /** Populates the main window with various controls.
     * 
     */
    private void populateWindow()
    {
        log.debug("Populating window.");
        
        pBar = new ProgressBar(shell, SWT.SMOOTH + SWT.HORIZONTAL);
        pBar.setMinimum(0);
        pBar.setMaximum(20);
        pBar.setSize(202, 29);
        pBar.setLocation(10, 10);
        
        final Label label = new Label(shell, SWT.CENTER);
        label.setText(localization.getString("LABEL_WAIT"));
        label.setSize(202, 20);
        label.setLocation(10, 45);
    }
    
    /** Moves the progress bar one step, or returns to the beginning if the end has been reached.
     * 
     */
    public void step()
    {
        if (windowThatCalled == null)
        {
            log.error("Attempt to call step() before calling start() first.");
            
            throw new NullPointerException();
        }
        
        int i = pBar.getSelection() + 1;
        
        if (i > pBar.getMaximum())
        {
            i = pBar.getMinimum();
        }
        
        pBar.setSelection(i);
    }
    
    /** Moves back the progress bar to the first step.
     * 
     */
    public void reset()
    {
        log.debug("Resetting progress bar position.");
        
        if (windowThatCalled == null)
        {
            log.error("Attempt to call reset() before calling start() first.");
            
            throw new NullPointerException();
        }
        
        pBar.setSelection(pBar.getMinimum());
    }
    
    /** Hides the given window and displays the wait window.
     * The calling window is hidden until it calls the {@link WWait#finish finish()} method of this class.
     * 
     * @param callingWindow Calling window shell
     * @throws ThreadAlreadyRunningException If a thread is already running
     */
    public void hideAndBegin(final Shell callingWindow) throws ThreadAlreadyRunningException
    {
        process(callingWindow, true);
    }
    
    /** Displays the wait window on top of the given window.
     * 
     * @param callingWindow Calling window shell
     * @throws ThreadAlreadyRunningException If a thread is already running
     */
    public void begin(final Shell callingWindow) throws ThreadAlreadyRunningException
    {
        process(callingWindow, false);
    }
    
    /** Displays the wait window on top of or in replacement of the given window, depending on the hide parameter.
     * 
     * @param callingWindow Calling window shell
     * @param hide <code>true</code> if the given window should be hidden when the wait window is displayed
     * @throws ThreadAlreadyRunningException If a thread is already running
     */
    private void process(final Shell callingWindow, final boolean hide) throws ThreadAlreadyRunningException
    {
        log.debug("begin()ning wait window.");
        
        if (windowThatCalled != null)
        {
            log.error("Attempt to call start() twice without stop()ping first.");
            
            throw new ThreadAlreadyRunningException();
        }
        
        log.trace("Creating new shell.");
        
        shell = new Shell(callingWindow, SWT.APPLICATION_MODAL + SWT.MODELESS);
        shell.setData(this);
        
        Position.centerElement(callingWindow, shell, WINDOW_WIDTH, WINDOW_HEIGHT);
        
        // Populating window
        populateWindow();
        
        windowThatCalled = callingWindow;
        if (hide)
        {
            windowThatCalled.setVisible(false);
        }
        
        log.trace("Opening shell");
        
        shell.open();
    }
    
    /** Stops displaying the wait window.
     * 
     * @throws NullPointerException If no window has launched the thread before
     */
    public void finish() throws NullPointerException
    {
        log.debug("finish()ing wait window.");
        
        if (windowThatCalled == null)
        {
            log.error("Attempt to call stop() before calling start() first.");
            
            throw new NullPointerException();
        }
        
        windowThatCalled.setVisible(true);
        windowThatCalled = null;
        
        shell.dispose();
        
        // Make it clean
        shell = null;
    }
}
